using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;
using System.IO;
using SDMX_ML.Framework.Interfaces;
using SDMX_Compact = SDMX_ML.Framework.Compact;
using SDMX_Message = SDMX_ML.Framework.Message;
using SDMX_Common = SDMX_ML.Framework.Common;


namespace SDMX_ML.Framework.Messages
{
    /// <summary>
    /// Compactdata message.
    /// </summary>
    public class CompactData : IMessage
    {
        #region Fields

        private const string PREFIX = "data";

        private XDocument _xmldoc;
        private XElement _xml;
        private XNamespace _nsQueryMessage;
        private XNamespace _nsCompact;
        private XNamespace _default;
        private XNamespace _nsDataSet;
        private string _prefixNSDataSet;
        private string _schemaLocation;
        private IEnumerable<XNode> nodes;
        private SDMX_Compact.DataSetType _dataset = null;
        private SDMX_Message.HeaderType _header = null;
        XElement _enDataSet;
        
        #endregion

        #region Constructors

        /// <summary>
        /// Creates objects from the xml file in the parameter
        /// </summary>
        /// <param name="xml">A SDMX_ML Compact data message.</param>
        public CompactData(string xml)
        {
            _xml = XElement.Parse(xml);
            
            setNameSpace();

            if (_header == null)
            {
                _header = new SDMX_Message.HeaderType(_xml);
            }

            setDataSet();
        }

        /// <summary>
        /// Creates a SDMX_ML CompactDataMessage that can write a xml file.
        /// </summary>
        public CompactData()
        {
            _header = new SDMX_Message.HeaderType();
        }

        /// <summary>
        /// Creates a SDMX_ML CompactDataMessage that can write a xml file.
        /// </summary>
        /// <param name="nameSpace">Namespace as an url used for a specific schema</param>
        /// <param name="schemaLocation">Location of a specific schema file</param>
        public CompactData(string nameSpace, string schemaLocation)
        {
            if (!String.IsNullOrEmpty(nameSpace) & !String.IsNullOrEmpty(schemaLocation))
            {
                _header = new SDMX_Message.HeaderType();
                _nsDataSet = nameSpace;
                _schemaLocation = schemaLocation;
                _prefixNSDataSet = PREFIX;
            }
            else
            {
                throw new Exception("Parameters nameSpace and schemaLocation are required fields.");
            }
        }

        /// <summary>
        /// Creates a SDMX_ML CompactDataMessage that can write a xml file.
        /// </summary>
        /// <param name="nameSpace">Namespace as an url used for a specific schema</param>
        /// <param name="schemaLocation">Location of a specific schema file</param>
        /// /// <param name="prefix">Preferred prefix to be used together with namespace.</param>
        public CompactData(string nameSpace, string schemaLocation, string prefix)
        {
            if (!String.IsNullOrEmpty(nameSpace) & !String.IsNullOrEmpty(schemaLocation) & !String.IsNullOrEmpty(prefix))
            {
                _header = new SDMX_Message.HeaderType();
                _nsDataSet = nameSpace;
                _schemaLocation = schemaLocation;
                _prefixNSDataSet = prefix;
            }
            else
            {
                throw new Exception("Parameters nameSpace, schemaLocation and prefix are required fields.");
            }
        }

        #endregion

        #region Create Objects from XML

        private void setNameSpace()
        {
            _nsQueryMessage = _xml.Name.Namespace;
            _nsCompact = _xml.GetNamespaceOfPrefix("compact");
            _default = _xml.GetDefaultNamespace();
            
             nodes = _xml.Nodes();

             foreach (XNode xn in nodes)
             {
                 XElement x = (XElement)xn;
                 XName xName = x.Name;
                 

                 if(xName.LocalName == "DataSet")
                 {
                    _nsDataSet = (XNamespace)xName.NamespaceName;
                    _prefixNSDataSet = x.GetPrefixOfNamespace(_nsDataSet);
                    

                    SDMX_Compact.CompactElement compactElement = new SDMX_Compact.CompactElement(xName.LocalName);

                     //Adding attributes that are not part of the known attributes that is already
                     //a part of the DataSet datatype.
                    foreach (XAttribute attribute in x.Attributes())
                    {
                        //Checking that the attribute is not already known.
                        if (ExistingAttribute(attribute.Name.LocalName) == false)
                        {
                            SDMX_Compact.CompactAttribute compactAttribute =
                                new SDMX_Compact.CompactAttribute(attribute.Name.LocalName, attribute.Value);
                            _dataset.CompactAttributes.Add(compactAttribute);
                        }
                    }

                    break;
                 }
             }
        }

        private bool ExistingAttribute(string attributeName)
        {
            bool exist = false;

            switch (attributeName)
            {
                case "keyFamilyURI":
                    exist = true;
                    break;
                case "datasetID":
                    exist = true;
                    break;
                case "dataProviderSchemeAgencyId":
                    exist = true;
                    break;
                case "dataProviderSchemeId":
                    exist = true;
                    break;
                case "dataProviderID":
                    exist = true;
                    break;
                case "dataflowAgencyID":
                    exist = true;
                    break;
                case "dataflowID":
                    exist = true;
                    break;
                case "action":
                    exist = true;
                    break;
                case "reportingBeginDate":
                    exist = true;
                    break;
                case "reportingEndDate":
                    exist = true;
                    break;
                case "validFromDate":
                    exist = true;
                    break;
                case "validToDate":
                    exist = true;
                    break;
                case "publicationYear":
                    exist = true;
                    break;
                case "publicationPeriod":
                    exist = true;
                    break;
                    
            }

            return exist;
        }

        private void setDataSet()
        {
 
            if (_xml.Element(_nsDataSet + "DataSet") != null)
            {
                _dataset = new SDMX_Compact.DataSetType();

                _enDataSet = _xml.Element(_nsDataSet + "DataSet");
            }

            XElement header = _xml.Element(_nsQueryMessage + "Header");

            if (header != null)
            {
                _header = new SDMX_Message.HeaderType(header);
            }
            

            if (_dataset != null)
            {
                if (_enDataSet.Attribute("keyFamilyURI") != null)
                {
                    if (!String.IsNullOrEmpty(_enDataSet.Attribute("keyFamilyURI").Value))
                    {
                        _dataset.KeyFamilyURI = _enDataSet.Attribute("keyFamilyURI").Value;
                    }
                }

                if (_enDataSet.Attribute("datasetID") != null)
                {
                    if (!String.IsNullOrEmpty(_enDataSet.Attribute("datasetID").Value))
                    {
                        SDMX_Common.IDType idType = new SDMX_Common.IDType();
                        idType.Value = _enDataSet.Attribute("datasetID").Value;
                        _dataset.DataSetID = idType;
                    }
                }

                if (_enDataSet.Attribute("dataProviderSchemeAgencyId") != null)
                {
                    if (!String.IsNullOrEmpty(_enDataSet.Attribute("dataProviderSchemeAgencyId").Value))
                    {
                        SDMX_Common.IDType idType = new SDMX_Common.IDType();
                        idType.Value = _enDataSet.Attribute("dataProviderSchemeAgencyId").Value;
                        _dataset.DataproviderSchemeAgencyId = idType;
                    }
                }

                if (_enDataSet.Attribute("dataProviderSchemeId") != null)
                {
                    if (!String.IsNullOrEmpty(_enDataSet.Attribute("dataProviderSchemeId").Value))
                    {
                        SDMX_Common.IDType idType = new SDMX_Common.IDType();
                        idType.Value = _enDataSet.Attribute("dataProviderSchemeId").Value;
                        _dataset.DataProviderSchemeId = idType;
                    }
                }

                if (_enDataSet.Attribute("dataProviderID") != null)
                {
                    if (!String.IsNullOrEmpty(_enDataSet.Attribute("dataProviderID").Value))
                    {
                        SDMX_Common.IDType idType = new SDMX_Common.IDType();
                        idType.Value = _enDataSet.Attribute("dataProviderID").Value;
                        _dataset.DataProviderId = idType;
                    }
                }

                if (_enDataSet.Attribute("dataflowAgencyID") != null)
                {
                    if (!String.IsNullOrEmpty(_enDataSet.Attribute("dataflowAgencyID").Value))
                    {
                        SDMX_Common.IDType idType = new SDMX_Common.IDType();
                        idType.Value = _enDataSet.Attribute("dataflowAgencyID").Value;
                        _dataset.DataflowAgencyID = idType;
                    }
                }

                if (_enDataSet.Attribute("dataflowID") != null)
                {
                    if (!String.IsNullOrEmpty(_enDataSet.Attribute("dataflowID").Value))
                    {
                        SDMX_Common.IDType idType = new SDMX_Common.IDType();
                        idType.Value = _enDataSet.Attribute("dataflowID").Value;
                        _dataset.DataflowID = idType;
                    }
                }

                if (_enDataSet.Attribute("action") != null)
                {
                    if (!String.IsNullOrEmpty(_enDataSet.Attribute("action").Value))
                    {
                        _dataset.Action = SDMX_Common.EnumerationConvert.EnumDataSetActionFromString(_enDataSet.Attribute("action").Value);
                    }
                }

                if (_enDataSet.Attribute("reportingBeginDate") != null)
                {
                    if (!String.IsNullOrEmpty(_enDataSet.Attribute("reportingBeginDate").Value))
                    {
                        SDMX_Common.TimePeriodType timePeriod = new SDMX_Common.TimePeriodType();
                        timePeriod.TimePeriod = _enDataSet.Attribute("reportingBeginDate").Value;
                        _dataset.ReportingBeginDate = timePeriod;
                    }
                }

                if (_enDataSet.Attribute("reportingEndDate") != null)
                {
                    if (!String.IsNullOrEmpty(_enDataSet.Attribute("reportingEndDate").Value))
                    {
                        SDMX_Common.TimePeriodType timePeriod = new SDMX_Common.TimePeriodType();
                        timePeriod.TimePeriod = _enDataSet.Attribute("reportingEndDate").Value;
                        _dataset.ReportingEndDate = timePeriod;
                    }
                }

                if (_enDataSet.Attribute("validFromDate") != null)
                {
                    if (!String.IsNullOrEmpty(_enDataSet.Attribute("validFromDate").Value))
                    {
                        SDMX_Common.TimePeriodType timePeriod = new SDMX_Common.TimePeriodType();
                        timePeriod.TimePeriod = _enDataSet.Attribute("validFromDate").Value;
                        _dataset.ValidFromDate = timePeriod;
                    }
                }

                if (_enDataSet.Attribute("validToDate") != null)
                {
                    if (!String.IsNullOrEmpty(_enDataSet.Attribute("validToDate").Value))
                    {
                        SDMX_Common.TimePeriodType timePeriod = new SDMX_Common.TimePeriodType();
                        timePeriod.TimePeriod = _enDataSet.Attribute("validToDate").Value;
                        _dataset.ValidToDate = timePeriod;
                    }
                }

                if (_enDataSet.Attribute("publicationYear") != null)
                {
                    if (!String.IsNullOrEmpty(_enDataSet.Attribute("publicationYear").Value))
                    {
                        _dataset.PublicationYear = _enDataSet.Attribute("publicationYear").Value;
                    }
                }

                if (_enDataSet.Attribute("publicationPeriod") != null)
                {
                    if (!String.IsNullOrEmpty(_enDataSet.Attribute("publicationPeriod").Value))
                    {
                        SDMX_Common.TimePeriodType timePeriod = new SDMX_Common.TimePeriodType();
                        timePeriod.TimePeriod = _enDataSet.Attribute("publicationPeriod").Value;
                        _dataset.PublicationPeriod = timePeriod;
                    }
                }

                AddElements(_enDataSet, null);
            }
        }

        private void AddElements(XElement element, SDMX_Compact.CompactElement compactElement)
        {
            foreach (XNode node in element.Nodes())
            {
                XElement e = (XElement)node;
                SDMX_Compact.CompactElement ce = new SDMX_Compact.CompactElement(e.Name.LocalName);

                AddAttributes(ref ce, e);

                AddElements(e, ce);

                if (compactElement != null)
                {
                    compactElement.Children.Add(ce);
                }
                else
                {
                    _dataset.CompactElements.Add(ce);
                }
                
            }
        }

        private void AddAttributes(ref SDMX_Compact.CompactElement compactElement, XElement element)
        {
            foreach (XAttribute attribute in element.Attributes())
            {
                SDMX_Compact.CompactAttribute compactAttribute =
                    new SDMX_Compact.CompactAttribute(attribute.Name.LocalName, attribute.Value);
                compactElement.Attributes.Add(compactAttribute);
            }
        }

        #endregion

        #region WriteXmlFile (From Objects to Xml)

        /// <summary>
        /// Message as xml in a string.
        /// </summary>
        /// <returns>Xml as string.</returns>
        public string ToXml()
        {
            XElement element = GetXml();

            return element.ToString();
        }

        /// <summary>
        /// Message as XElement.
        /// </summary>
        /// <returns>XElement.</returns>
        public XElement GetXml()
        {
            _xmldoc = new XDocument(
                new XDeclaration("1.0", "UTF-8", "yes"));

            XElement compactdata = null;

            //XAttribute schemaLocation = null;

            //if _xml variable is not null it is because a SDMX xml file was used
            //as input and in that case we try to use information about namespace
            //and schema location from the original xml file.
            if (_xml != null)
            {
                foreach (XAttribute a in _xml.Attributes())
                {
                    if (a.Name.LocalName == "schemaLocation")
                    {
                        _schemaLocation = a.Value;
                        break;
                    }
                }
            }

            compactdata = new XElement(Namespaces.GetNamespaces("CompactData"));
            compactdata.Add(new XAttribute(XNamespace.Xmlns + _prefixNSDataSet, _nsDataSet));
            compactdata.Add(new XAttribute(Namespaces.GetXSI() + "schemaLocation", _schemaLocation));


            if (_header != null)
            {
                compactdata.Add(_header.GetXml());
            }

            if (_dataset != null)
            {
                compactdata.Add(GetdataSet());
            }

            return compactdata;
        }

        private XElement GetdataSet()
        {
            _enDataSet = new XElement(_nsDataSet + "DataSet");

            if (!String.IsNullOrEmpty(_dataset.KeyFamilyURI))
            {
                _enDataSet.Add(new XAttribute("keyFamilyURI", _dataset.KeyFamilyURI));
            }

            if (_dataset.DataSetID != null)
            {
                if (!String.IsNullOrEmpty(_dataset.DataSetID.Value))
                {
                    _enDataSet.Add(new XAttribute("dataSetID", _dataset.DataSetID.Value));
                }
            }

            if (_dataset.DataproviderSchemeAgencyId != null)
            {
                if (!String.IsNullOrEmpty(_dataset.DataproviderSchemeAgencyId.Value))
                {
                    _enDataSet.Add(new XAttribute("dataProviderSchemeAgencyId", _dataset.DataproviderSchemeAgencyId.Value));
                }
            }

            if (_dataset.DataProviderSchemeId != null)
            {
                if (!String.IsNullOrEmpty(_dataset.DataProviderSchemeId.Value))
                {
                    _enDataSet.Add(new XAttribute("dataProviderSchemeId", _dataset.DataProviderSchemeId.Value));
                }
            }

            if (_dataset.DataProviderId != null)
            {
                if (!String.IsNullOrEmpty(_dataset.DataProviderId.Value))
                {
                    _enDataSet.Add(new XAttribute("dataProviderID", _dataset.DataProviderId.Value));
                }
            }

            if (_dataset.DataflowAgencyID != null)
            {
                if (!String.IsNullOrEmpty(_dataset.DataflowAgencyID.Value))
                {
                    _enDataSet.Add(new XAttribute("dataflowAgencyID", _dataset.DataflowAgencyID.Value));
                }
            }

            if (_dataset.DataflowID != null)
            {
                if (!String.IsNullOrEmpty(_dataset.DataflowID.Value))
                {
                    _enDataSet.Add(new XAttribute("dataflowID", _dataset.DataflowID.Value));
                }
            }

            if (_dataset.Action != null)
            {
                _enDataSet.Add(new XAttribute("action", _dataset.Action.ToString()));
            }

            if (_dataset.ReportingBeginDate != null)
            {
                if (!String.IsNullOrEmpty(_dataset.ReportingBeginDate.TimePeriod))
                {
                    _enDataSet.Add(new XAttribute("reportingBeginDate", _dataset.ReportingBeginDate.TimePeriod));
                }
            }

            if (_dataset.ReportingEndDate != null)
            {
                if (!String.IsNullOrEmpty(_dataset.ReportingEndDate.TimePeriod))
                {
                    _enDataSet.Add(new XAttribute("reportingEndDate", _dataset.ReportingEndDate.TimePeriod));
                }
            }

            if (_dataset.ValidFromDate != null)
            {
                if (!String.IsNullOrEmpty(_dataset.ValidFromDate.TimePeriod))
                {
                    _enDataSet.Add(new XAttribute("validFromDate", _dataset.ValidFromDate.TimePeriod));
                }
            }

            if (_dataset.ValidToDate != null)
            {
                if (!String.IsNullOrEmpty(_dataset.ValidToDate.TimePeriod))
                {
                    _enDataSet.Add(new XAttribute("validToDate", _dataset.ValidToDate.TimePeriod));
                }
            }

            if (_dataset.PublicationYear != null)
            {
                if (!String.IsNullOrEmpty(_dataset.PublicationYear))
                {
                    _enDataSet.Add(new XAttribute("publicationYear", _dataset.PublicationYear));
                }
            }

            if (_dataset.PublicationPeriod != null)
            {
                if (!String.IsNullOrEmpty(_dataset.PublicationPeriod.TimePeriod))
                {
                    _enDataSet.Add(new XAttribute("publicationPeriod", _dataset.PublicationPeriod.TimePeriod));
                }
            }

            foreach (SDMX_Compact.CompactAttribute att in _dataset.CompactAttributes)
            {
                _enDataSet.Add(new XAttribute(att.AttributeName, att.AttributeValue));
            }

            GetElements(_dataset.CompactElements, null);

            return _enDataSet;
        }

        private void GetElements(List<SDMX_Compact.CompactElement> compactElements, XElement rootElement)
        {
            XElement element = null;
            int count = 0;
            foreach (SDMX_Compact.CompactElement compactElement in compactElements)
            {
                count++;

                element = new XElement(_nsDataSet + compactElement.Name);

                foreach(SDMX_Compact.CompactAttribute attribute in compactElement.Attributes)
                {
                    element.Add(new XAttribute(attribute.AttributeName, attribute.AttributeValue));
                }

                if (compactElement.Children.Count > 0)
                {
                    GetElements(compactElement.Children, element);

                    _enDataSet.Add(element);
                }
                else
                {
                    if (rootElement != null)
                    {
                        rootElement.Add(element);
                    }
                    else
                    {
                        _enDataSet.Add(element);
                    }
                }
            }

            
        }

        #endregion

        #region Properties

        /// <summary>
        /// Dataset contaning data.
        /// </summary>
        public SDMX_Compact.DataSetType Dataset
        {
            get { return _dataset; }
            set { _dataset = value; }
        }

        /// <summary>
        /// Header for message.
        /// </summary>
        public SDMX_Message.HeaderType Header
        {
            get { return _header; }
            set { _header = value; }
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Writes the content to a xml file.
        /// </summary>
        /// <param name="file">Path and name of file.</param>
        /// <param name="content">Content of a SDMX_ML file.</param>
        public void Write(string file, string content)
        {
            try
            {
                using (TextWriter writer = new StreamWriter(file))
                {
                    try
                    {
                        writer.Write(content);
                    }
                    catch (Exception ex)
                    {
                        throw new Exception("Unexpected error when wrting to file. " + ex.Message);
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception("Unexpected error when creating file. " + ex.Message);
            }
        }
        /// <summary>
        /// Load SDMX_ML CompactData file into CompactData object.
        /// </summary>
        /// <param name="file">Path and name of file.</param>
        /// <returns></returns>
        public static CompactData Load(string file)
        {
            CompactData compactData = null;

            if (File.Exists(file))
            {
                using (TextReader reader = new StreamReader(file))
                {
                    string fileContent = reader.ReadToEnd();

                    try
                    {
                        compactData = new CompactData(fileContent);
                    }
                    catch (Exception ex)
                    {
                        throw new Exception("Unexpected error when creating CompactData object. " + ex.Message);
                    }
                }

                return compactData;
            }
            else
            {
                throw new FileNotFoundException("File not found when trying to create CompactData object.", file);
            }
        }

        #endregion

    }
}
